Skip to content
标签
爬虫
工具
字数
5493 字
阅读时间
23 分钟

网络爬虫

概述

在大数据时代,信息的采集是一项重要的工作,而互联网中的数据是海量的,如果单纯靠人力进行信息采集,不仅低效繁琐,搜集的成本也会提高。如何自动高效地获取互联网中我们感兴趣的信息并为我们所用是一个重要的问题,而爬虫技术就是为了解决这些问题而生的。

网络爬虫(Web crawler)也叫做网络机器人,可以代替人们自动地在互联网中进行数据信息的采集与整理。它是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本,可以自动采集所有其能够访问到的页面内容,以获取或更新这些网站的内容和检索方式。

从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。

应用

  1. 可以实现搜索引擎

我们学会了爬虫编写之后,就可以利用爬虫自动地采集互联网中的信息,采集回来后进行相应的存储或处理,在需要检索某些信息的时候,只需在采集回来的信息中进行检索,即实现了私人的搜索引擎。

  1. 大数据时代,可以让我们获取更多的数据源。

在进行大数据分析或者进行数据挖掘的时候,需要有数据源进行分析。我们可以从某些提供数据统计的网站获得,也可以从某些文献或内部资料中获得,但是这些获得数据的方式,有时很难满足我们对数据的需求,而手动从互联网中去寻找这些数据,则耗费的精力过大。此时就可以利用爬虫技术,自动地从互联网中获取我们感兴趣的数据内容,并将这些数据内容爬取回来,作为我们的数据源,再进行更深层次的数据分析,并获得更多有价值的信息。

  1. 可以更好地进行搜索引擎优化(SEO)。

对于很多SEO从业者来说,为了更好的完成工作,那么就必须要对搜索引擎的工作原理非常清楚,同时也需要掌握搜索引擎爬虫的工作原理。

而学习爬虫,可以更深层次地理解搜索引擎爬虫的工作原理,这样在进行搜索引擎优化时,才能知己知彼,百战不殆。

分类

通用网络爬虫

通用网络爬虫又称全网爬虫(Scalable Web Crawler),爬行对象从一些种子 URL 扩充到整个 Web,主要为门户站点搜索引擎和大型 Web 服务提供商采集数据。

这类网络爬虫的爬行范围和数量巨大,对于爬行速度和存储空间要求较高,对于爬行页面的顺序要求相对较低,同时由于待刷新的页面太多,通常采用并行工作方式,但需要较长时间才能刷新一次页面。

简单的说就是互联网上抓取所有数据。

聚焦网络爬虫

聚焦网络爬虫(Focused Crawler),又称主题网络爬虫(Topical Crawler),是指选择性地爬行那些与预先定义好的主题相关页面的网络爬虫。

和通用网络爬虫相比,聚焦爬虫只需要爬行与主题相关的页面,极大地节省了硬件和网络资源,保存的页面也由于数量少而更新快,还可以很好地满足一些特定人群对特定领域信息的需求 。

简单的说就是互联网上只抓取某一种数据。

增量式网络爬虫

增量式网络爬虫(Incremental Web Crawler)是 指 对 已 下 载 网 页 采 取 增量式更新和只爬行新产生的或者已经发生变化网页的爬虫,它能够在一定程度上保证所爬行的页面是尽可能新的页面。

和周期性爬行和刷新页面的网络爬虫相比,增量式爬虫只会在需要的时候爬行新产生或发生更新的页面 ,并不重新下载没有发生变化的页面,可有效减少数据下载量,及时更新已爬行的网页,减小时间和空间上的耗费,但是增加了爬行算法的复杂度和实现难度。

简单的说就是互联网上只抓取刚刚更新的数据。

Deep Web 爬虫

Web 页面按存在方式可以分为表层网页(Surface Web)和深层网页(Deep Web,也称 Invisible Web Pages 或 Hidden Web)。

表层网页是指传统搜索引擎可以索引的页面,以超链接可以到达的静态网页为主构成的 Web 页面。

Deep Web 是那些大部分内容不能通过静态链接获取的、隐藏在搜索表单后的,只有用户提交一些关键词才能获得的 Web 页面。

去重过滤器

在使用网络爬虫过程中,去重是一个不可避免的问题,这里需要对抓取的数据内容进行过滤,就是对车辆幸好名称进行去重过滤,避免同样条数据反复保存到数据库中。

传统的去重,可以使用Map或者Set集合、哈希表的方式来实现去重,在数据量较小的情况下,使用这种方式没有问题。可是当我们需要大量爬去数据的时候,这种方式就存在很大问题。因为会极大的占用内存和系统资源,导致爬虫系统崩溃。这里将会使用布隆过滤器

Bloom过滤器介绍

布隆过滤器 (Bloom Filter)是由Burton Howard Bloom于1970年提出,它是一种space efficient的概率型数据结构,用于判断一个元素是否在集合中。在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等经常被用到。

哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就能完成同样的问题。布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,误报率越大,但是漏报是不可能的。

布隆过滤器原理

布隆过滤器需要的是一个位数组(和位图类似)和K个映射函数(和Hash表类似),在初始状态时,对于长度为m的位数组array,它的所有位被置0。

img

对于有n个元素的集合S={S1,S2...Sn},通过k个映射函数{f1,f2,......fk},将集合S中的每个元素Sj(1<=j<=n)映射为K个值{g1,g2...gk},然后再将位数组array中相对应的array[g1],array[g2]......array[gk]置为1:

img

如果要查找某个元素item是否在S中,则通过映射函数{f1,f2,...fk}得到k个值{g1,g2...gk},然后再判断array[g1],array[g2]...array[gk]是否都为1,若全为1,则item在S中,否则item不在S中。

布隆过滤器会造成一定的误判,因为集合中的若干个元素通过映射之后得到的数值恰巧包括g1,g2,...gk,在这种情况下可能会造成误判,但是概率很小。

布隆过滤器实现

java
//去重过滤器,布隆过滤器
public class TitleFilter {

	/* BitSet初始分配2^24个bit */
	private static final int DEFAULT_SIZE = 1 << 24;

	/* 不同哈希函数的种子,一般应取质数 */
	private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37 };

	private BitSet bits = new BitSet(DEFAULT_SIZE);

	/* 哈希函数对象 */
	private SimpleHash[] func = new SimpleHash[seeds.length];

	public BloomFilter() {
		for (int i = 0; i < seeds.length; i++) {
			func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
		}
	}

	// 将url标记到bits中
	public void add(String str) {
		for (SimpleHash f : func) {
			bits.set(f.hash(str), true);
		}
	}

	// 判断是否已经被bits标记
	public boolean contains(String str) {
		if (StringUtils.isBlank(str)) {
			return false;
		}

		boolean ret = true;
		for (SimpleHash f : func) {
			ret = ret && bits.get(f.hash(str));
		}

		return ret;
	}

	/* 哈希函数类 */
	public static class SimpleHash {
		private int cap;
		private int seed;

		public SimpleHash(int cap, int seed) {
			this.cap = cap;
			this.seed = seed;
		}

		// hash函数,采用简单的加权和hash
		public int hash(String value) {
			int result = 0;
			int len = value.length();
			for (int i = 0; i < len; i++) {
				result = seed * result + value.charAt(i);
			}
			return (cap - 1) & result;
		}
	}
}

初始化去重过滤器

项目一启动,就应该创建去重过滤器。

编写以下代码实现过滤器初始化

java
@Configuration
public class TitleFilterCfg {

	@Autowired
	private CarTestService carTestService;

	@Bean
	public TitleFilter titleFilter() {
		// 创建车辆标题过滤器
		TitleFilter titleFilter = new TitleFilter();

		// 从数据库查询车辆标题,分页查询
		List<String> list = this.carTestService.queryByPage(1, 5000);

		// 遍历查询结果
		for (String str : list) {
			// 把查询到的数据放到过滤器中
			titleFilter.add(str);
		}

		// 返回创建好的过滤器
		return titleFilter;
	}

}

httpClient+Jsoup爬虫

可结合定时任务定时爬取

java
@Test
	public void testCrawlerAutohome() throws Exception {
		//遍历所有的url
		for (int i = 1; i < 139; i++) {
			// 返回数据
			String html = this.apiService.getHtml("https://www.autohome.com.cn/bestauto/" + i);

			Document doc = Jsoup.parse(html);

			// 获取每获取评测信息
			Elements cars = doc.select("#bestautocontent div.uibox");

			// 遍历评测信息
			for (Element car : cars) {
				// 去重判读
				String title = car.getElementsByClass("uibox-title uibox-title-border").text();
				if (this.titleFilter.contains(title)) {
					// 如果包含了,就不保存了,遍历下一个
					continue;
				}

				// 创建评测对象,封装数据
				CarTest carTest = this.copyCarTest(car);

				// 评测图片,下载图片
				String image = this.getImage(car);

				// 设置图片
				carTest.setImage(image);

				// 保存数据
				this.saveCarTest(carTest);
			}
		}
	}

WebCollector

WebCollector 是一个无须配置、便于二次开发的 Java 爬虫框架(内核),它提供精简的的 API,只需少量代码即可实现一个功能强大的爬虫。

WebCollector 致力于维护一个稳定、可扩的爬虫内核,便于开发者进行灵活的二次开发。内核具有很强的扩展性,用户可以在内核基础上开发自己想要的爬虫。源码中集成了 Jsoup,可进行精准的网页解析。

最新的参考文档

https://github.com/CrawlScript/WebCollector/blob/master/README.md

特性:

  1. 自定义遍历策略,可完成更为复杂的遍历业务,例如分页、AJAX

  2. 可以为每个 URL 设置附加信息(MetaData),利用附加信息可以完成很多复杂业务,例如深度获取、锚文本获取、引用页面获取、POST 参数传递、增量更新等。

  3. 使用插件机制,用户可定制自己的Http请求、过滤器、执行器等插件。

  4. 内置一套基于内存的插件(RamCrawler),不依赖文件系统或数据库,适合一次性爬取,例如实时爬取搜索引擎。

  5. 内置一套基于 Berkeley DB(BreadthCrawler)的插件:适合处理长期和大量级的任务,并具有断点爬取功能,不会因为宕机、关闭导致数据丢失。

  6. 集成 selenium,可以对 JavaScript 生成信息进行抽取

  7. 可轻松自定义 http 请求,并内置多代理随机切换功能。 可通过定义 http 请求实现模拟登录。

  8. 使用 slf4j 作为日志门面,可对接多种日志

  9. 使用类似Hadoop的Configuration机制,可为每个爬虫定制配置信息。

爬取逻辑

WebCollector和Nutch一样,把爬虫的广度遍历拆分成了分层的操作。

第一层:爬取网页,http://news.hfut.edu.cn/,解析网页,获取多个链接,将这些链接保存到CrawlDB中,设置状态为未爬取。同时将http://news.hfut.edu.cn/的爬取状态设置为已爬取。结束第一轮。

第二层,找到CrawlDB中状态为未爬取的页面(第一层解析出来的多个链接),分别爬取,并解析网页,获得所有的链接。和第一层操作一样,将解析出的链接放入CrawlDB,设置为未爬取,并将第二层爬取的页面,状态设置为已爬取。

第三层,找到CrawlDB中状态为未爬取的页面(第二层解析出来的链接).................

每一层都可以作为一个独立的任务去运行,所以可以将一个大型的广度遍历任务,拆分成一个一个小任务。爬虫里有个参数,设置爬取的层数,指的就是这个。

插件机制:

只需要自定义一个实现相关接口的类,并在相关Factory内指定即可。WebCollector内置了一套插件(cn.edu.hfut.dmic.webcollector.plugin.redis)。基于这套插件,可以把WebCollector的任务管理放到redis数据库上,这使得WebCollector可以爬取海量的数据(上亿级别)。

使用

添加依赖

xml
<dependencies>
    <dependency>
        <groupId>cn.edu.hfut.dmic.webcollector</groupId>
        <artifactId>WebCollector</artifactId>
        <version>2.71</version>
    </dependency>
</dependencies>

逻辑

抓取内容

java
public class NewsListCrawler extends BreadthCrawler {

	public NewsListCrawler(String crawlPath, boolean autoParse) {
		super(crawlPath, autoParse);

		// 添加多个下载的url种子,这是这些种子的type是list
		for (int i = 0; i < 5; i++) {
			this.addSeed("http://news.hfut.edu.cn/list-" + i + "-1.html", "list");
		}

		// 设置多线程抓取
		this.setThreads(10);

	}

	public void visit(Page page, CrawlDatums next) {
		String url = page.url();
		// 判断抓取的url所属的type
		if (page.matchType("list")) {
			// 如果是list类型,则解析其中的url,声明为news类型
			next.add(page.links("div.col-lg-8 > ul > li > a")).type("news");
		} else if (page.matchType("news")) {
			// 判断是否是新闻页面
			// 获取输出文件路径
			File file = this.getConf().get("file");

			try {
				// 设置url
				FileUtils.writeStringToFile(file, url + "\r\n", "UTF-8", true);

				// 获取新闻标题
				String title = page.select("#Article > h2").first().text();
				FileUtils.writeStringToFile(file, title + "\r\n", "UTF-8", true);

				// 获取新闻正文
				String content = page.select("#artibody").first().text();
				FileUtils.writeStringToFile(file, content + "\r\n", "UTF-8", true);

			} catch (Exception e) {
				e.printStackTrace();
			}

		}
	}

	public static void main(String[] args) throws Exception {
		NewsListCrawler newsListCrawler = new NewsListCrawler("listCrawl", false);

		// 设置文件名称
		newsListCrawler.getConf().set("file", new File("C:/Users/tree/Desktop/123.txt"));

		newsListCrawler.start(2);
	}

}

抓取图片

自动方式

java
public class ImageCrawler extends BreadthCrawler {

	public ImageCrawler(String crawlPath, boolean autoParse) {
		super(crawlPath, autoParse);

		// 设置种子
		for (int i = 1; i < 7; i++) {
			super.addSeed("https://car.autohome.com.cn/jingxuan/list-11-p" + i + ".html");
		}

		// 设置正则,解析哪些url
		// 设置访问的图片页面
		super.addRegex("https://car[0-9]?.autoimg.cn/.*[jpg|png|jpeg|gif]");

		this.setThreads(1);
	}

	public void visit(Page page, CrawlDatums next) {
		// 根据http头中的Content-Type信息来判断当前资源是网页还是图片
		String contentType = page.contentType();

		// 根据Content-Type判断是否为图片
		if (contentType != null && contentType.startsWith("image")) {
			// 从Content-Type中获取图片扩展名
			String extName = "." + contentType.split("/")[1];

			try {
				// 获取图片数据
				byte[] image = page.content();
				// 根据图片MD5生成文件名
				String fileName = UUID.randomUUID().toString() + extName;
				// 声明图片文件
				File imageFile = new File("C:/Users/tree/Desktop/123/" + fileName);
				// 写入数据
				FileUtils.writeByteArrayToFile(imageFile, image);

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) throws Exception {
		// 创建爬虫
		ImageCrawler imageCrawler = new ImageCrawler("crawl", true);

		// 设置开始解析图片
		imageCrawler.getConf().setAutoDetectImg(true);

		// 设置断点续传
		imageCrawler.setResumable(true);

		// 启动爬虫
		imageCrawler.start(2);

	}

}

手动方式

java
public class BigImageCrawler extends BreadthCrawler {

	public BigImageCrawler(String crawlPath, boolean autoParse) {
		super(crawlPath, autoParse);

		// 设置种子
		for (int i = 1; i < 2; i++) {
			super.addSeed("https://car.autohome.com.cn/jingxuan/list-11-p" + i + ".html", "list");
		}

		// 设置正则,解析哪些url
		// 设置访问的图片页面
		// super.addRegex("https://car[0-9]?.autoimg.cn/.*[jpg|png|jpeg|gif]");

		this.setThreads(1);
	}

	public void visit(Page page, CrawlDatums next) {
		String url = page.url();
		if (page.matchType("list")) {
			next.add(new CrawlDatums(page.links("body > div.section > ul.content > li > a"), "imagePage"));
		} else if (page.matchType("imagePage")) {
			String imageUrl = "https:" + page.select("#img").attr("src");
			next.add(imageUrl);
			System.out.println("imagePage");
		} else if (page.matchUrl("https://car[0-9].autoimg.cn.*")) {
			// 根据http头中的Content-Type信息来判断当前资源是网页还是图片
			String contentType = page.contentType();

			// 根据Content-Type判断是否为图片
			if (contentType != null && contentType.startsWith("image")) {
				// 从Content-Type中获取图片扩展名
				String extName = "." + contentType.split("/")[1];

				try {
					// 获取图片数据
					byte[] image = page.content();
					// 根据图片MD5生成文件名
					String fileName = UUID.randomUUID().toString() + extName;
					// 声明图片文件
					File imageFile = new File("C:/Users/tree/Desktop/123/" + fileName);
					// 写入数据
					FileUtils.writeByteArrayToFile(imageFile, image);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}

		}

		System.out.println(url);

	}

	public static void main(String[] args) throws Exception {
		// 创建爬虫
		BigImageCrawler imageCrawler = new BigImageCrawler("crawl", true);

		// 设置开始解析图片
		imageCrawler.getConf().setAutoDetectImg(true);

		// 设置断点续传
		// imageCrawler.setResumable(true);

		// 启动爬虫
		imageCrawler.start(3);

	}

}

phantomJs+selenium解析动态网页

(1)一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。

(2)提供javascript API接口,即通过编写js程序可以直接与webkit内核交互,在此之上可以结合java语言等,通过java调用js等相关操作,从而解决了以前c/c++才能比较好的基于webkit开发优质采集器的限制。

(3)提供windows、linux、mac等不同os的安装使用包,也就是说可以在不同平台上二次开发采集项目或是自动项目测试等工作。

phantomJs官网

selenium官网

依赖

xml
<!-- selenium -->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.4.0</version>
</dependency>
<!-- phantomjsdriver -->
<dependency>
    <groupId>com.codeborne</groupId>
    <artifactId>phantomjsdriver</artifactId>
    <version>1.4.3</version>
</dependency>

demo

java
public static void main(String[] args) { // 设置必要参数
		DesiredCapabilities dcaps = new DesiredCapabilities();
		// ssl证书支持
		dcaps.setCapability("acceptSslCerts", true);
		// css搜索支持
		dcaps.setCapability("cssSelectorsEnabled", true);
		// js支持
		dcaps.setJavascriptEnabled(true);
		// 驱动支持
		dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,
				"E:\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe");
		// // 创建无界面浏览器对象
		WebDriver driver = new PhantomJSDriver(dcaps);

		try {
			// 让浏览器访问空间主页
			driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
			driver.get("https://item.jd.com/4391570.html");
			Thread.sleep(5000l);
			String html = driver.getPageSource();
			Writer out = new FileWriter(new File("D:/httpclient.html"));
			out.write(html);
			out.close();

			WebElement element = driver
					.findElement(By.xpath("/html/body/div[5]/div/div[2]/div[4]/div/div[1]/div[2]/span[1]/span[2]"));
			System.out.println(element.getText());

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			// 关闭并退出浏览器
			driver.close();
			driver.quit();
		}
	}

其他信息提取

图像OCR识别

从图片中识别出字符叫做光学字符识别(Optical Character Recognition)简称OCR。是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。即,针对印刷体字符,采用光学的方式将纸质文档中的文字转换成为黑白点阵的图像文件,并通过识别软件将图像中的文字转换成文本格式。

文字识别包括以下几个步骤:

(1)图文输入

是指通过输入图片到计算机中,也就是获取图片。

(2)预处理

​ 扫描一幅简单的印刷文档的图像,将每一个文字图像分检出来交给识别模块识别,这一过程称为图像预处理。预处理是指在进行文字识别之前的一些准备工作,包括图像净化处理,去掉原始图像中的显见噪声(干扰),

(3)单字识别

单字识别是体现OCR文字识别的核心技术。从扫描文本中分检出的文字图像,由计算机将其图形,图像转变成文字的标准代码.,是让计算机“认字”的关键,也就是所谓的识别技术。

识别技术就是特征比较技术,通过和识别特征库的比较,找到特征最相似的字,提取该文字的标准代码,即为识别结果。

(4)后处理

后处理是指对识别出的文字或多个识别结果采用词组方式进行上下匹配,即将单字识别的结果进行分词,与词库中的词组进行比较,以提高系统的识别率,减少误识率。 汉字字符识别是文字识别领域最为困难的问题,它涉及模式识别,图像处理,数字信号处理,自然语言理解,人工智能,模糊数学,信息论,计算机,中文信息处理等学科,是一门综合性技术。

Tess4J

Tess4J是一个OCR图片识别技术,我们这里使用的是Tess4J-3.4.2。在windows使用前必须安装Visual C++ 2015 Redistributable Packages,下载地址:

https://www.microsoft.com/zh-CN/download/details.aspx?id=48145)

依赖

xml
<dependency>
    <groupId>net.sourceforge.tess4j</groupId>
    <artifactId>tess4j</artifactId>
    <version>3.4.2</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

demo

java
public static void main(String[] args) throws Exception {
	CloseableHttpClient httpClient = HttpClients.createDefault();
	String url = "https://proxy.mimvp.com/common/ygrandimg.php?id=17&port=MmDiZmtvapW12cDMxMjgO0O";
	HttpGet httpGet = new HttpGet(url);
	CloseableHttpResponse response = httpClient.execute(httpGet);
	HttpEntity httpEntity = response.getEntity();
	InputStream inputStream = httpEntity.getContent();
	BufferedImage image = ImageIO.read(inputStream);
	// BufferedImage image = ImageIO.read(new
	// File("C:/Users/tree/Desktop/53281.png"));

	image = image.getSubimage(4, 8, 42, 17);
	Image scaledInstance = image.getScaledInstance(46, 25, image.SCALE_SMOOTH);// 设置缩放目标图片模板

	AffineTransformOp ato = new AffineTransformOp(AffineTransform.getScaleInstance(2.5, 2.5), null);
	scaledInstance = ato.filter(image, null);

	File file = new File("tessdata/temp.jpg");
	ImageIO.write((BufferedImage) scaledInstance, "jpg", file);

	ITesseract instance = new Tesseract();
	instance.setLanguage("eng");
	long startTime = System.currentTimeMillis();
	String ocrResult = instance.doOCR(file);
	// 输出识别结果
	System.out.println("OCR Result: \n" + ocrResult + "耗时:" + (System.currentTimeMillis() - startTime) + "ms");
}

从非html提取文本

PDF文件

PDFBox(https://pdfbox.apache.org/)就是专门用来解析PDF文件的Java项目。

Word、Excel文件

除了POI项目,还有开源的jxl可以用来读写Excel。

提取地域信息

ip地址

服务商可提供ip和地址的对应关系。ip库下载,例如纯真ip(http://www.cz88.net/),我们也可以用其作为离线ip库

电话号码

也可以通过手机电话号码的前七位确定其地址